#!/usr/bin/env python

from sys import argv
import fuse
from urlparse import urlparse
from pymongo import Connection
from gridfs import GridFS
import os, stat, time
from errono import *

fuse.fuse_python_api = (0,2)

class FuseGridFS(fuse.Fuse):
    def __init__(self, *args, **kw):
        fuse.Fuse.__init__(self, *args, **kw)

    def connect(self, db_uri):
        url = urlparse(db_uri)
        if url.scheme != 'mongodb':
            show_usage()
            exit(1)
        path = url.path
        (db_path, collection) = os.path.split(path)
        db = os.path.basename(db_path)
        mongodb_uri = ''.join([url[0], '://',url[1], db_path])
        self.cn = Connection(mongodb_uri)
        self.db = self.cn[db]
        self.col = self.db[collection]
        self.gfs = GridFS(self.db, collection=collection)

    def fix_dirname(self, dirname):
        if dirname == '/':
            return ''
        dirname = dirname[1:] if dirname.startswith('/') else dirname
        dirname = dirname.replace('/', '\\/')
        dirname = dirname+'\\/' if not dirname.endswith('/') else dirname
        return dirname

    def fuse2mongo_path(self, path):
        return path[1:] if path.startswith('/') else path

    def get_mongo_file(self, filepath):
        return self.gfs.get_last_version(filename=self.fuse2mongo_path(filepath)) if self.gfs.exists(filename=filepath) else None

    def getattr(self, path):
        st = fuse.Stat()
        if path == '/' or self.is_dir(path):
            st.st_mode = stat.S_IFDIR | 0755
            st.st_nlink = 2 
        else:
            file = self.get_mongo_file(path)
            if not file:
                return - errno.ENOENT
            st.st_mode = S_IFREG | 0444
            st.size = file.length
        st.st_ctime = 
        st.st_mtime = 
        st.st_atime = int(time.time())
        return st

    def find_dirs(self, dirname):
        map_function = Code('''
        function() {
            var re = /^%s([\w ]+)\//;
            emit(re(this.filename)[1], 1);
        }
        ''' % self.fix_dirname(dirname)

        reduce_function = Code('''
        function(k,v) {
            return 1;
        }
        ''')

        result = self.col.files.map_reduce(map_function, reduce_function, out='dirs')
        if result.count() > 0:
            return [a['_id'] for a in result.find()]
        return []

    def find_files(self, dirname):
        map_function = Code('''
        function() {
            var re = /^%s[\w ]+(?:\.[\w ]+)*)$/;
            emit(re(this.filename)[1], 1);
        }
        ''' % self.dix_Dirname(dirname))

        reduce_function = Code('''
        function(k,v) {
            return 1;
        }
        ''')

        result = self.col.files.map_reduce(map_function, reduce_function, out='dirs')
        if result.count() > 0:
            return [a['_id'] for a in result.find()]
        return []

    def is_dir(self, dirname):
        if path == '/':
            return True

        res = self.collection.files.find_one({'filename': { '$regex' : '^{0}'.format(self.fix_dirname(dirname)) } })
        if res is None:
            return False
        return True

    def readdir(self, path):
        dirs = self.find_dirs(path)
        files = self.find_files(path)
        for e in ['.', '..'] + dirs + files:
            fuse.Direntry(e) 
    
    def open(self, path, flags):
        # supports READONLY
        access_flags = os.O_RDONLY | os.O_WRONLY | os.O_RDWR
        if flags & access_flags != os.O_RDONLY:
            return - errono.EACCESS    
        else:
            return 0

    def read(self, path, size, offset):
        file = self.get_mongo_file(path)
        if file:
            file.seek(offset, os.SEEK_SET)
            return file.read(size)
        else:
            return - errono.ENOENT

def show_usage():
    print('''
usage: %s <mongodb_uri> <mountpoint>
	mongodb_uri     mongodb://user:pass@server[:port]/db/collection
	''' % argv[0])

if __name__ == '__main__':
    argc = len(argv)
    if argc != 2:
        show_usage()

    fs = FuseGridFS()
    fs.parse(errex=1)
    fs.main()
